package com.example.demo.code.aop.config;

import com.example.demo.code.aop.annotation.Action;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 配置切面
 *
 * @author huangchenyu
 * @date 2021-04-12
 */
@Aspect
@Component
public class AspectConfig {

    /**
     * 切点
     * 此处的切点是注解的方式，也可以用包名的方式达到相同的效果
     * @Pointcut("execution(public * com.gaopeng.springboot.mytest.controller.*.*(..))")
     */
    @Pointcut("@annotation(com.example.demo.code.aop.annotation.Action)")
    public void excute() {}

    /**
     * 前置通知
     */
    @Before(value = "excute()")
    public void before(JoinPoint joinPoint) {
        System.out.println("执行方法前：" + joinPoint);
    }

    /**
     * 环绕通知
     * @param proceedingJoinPoint 环绕通知切点信息
     */
    @Around(value = "excute()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("----------------环绕通知开始---------------");
        try {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            String ipAddr = getIpAddr(request);
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            System.out.println("执行方法：" + signature.getName());
            Map<String, Object> params = getNameAndValue(proceedingJoinPoint);
            System.out.println("请求参数：" + params.toString());
            Action action = signature.getMethod().getAnnotation(Action.class);
            System.out.println("菜单：" + action.description());
            Object object = proceedingJoinPoint.proceed();
            System.out.println("环绕通知结束，方法返回:" + object);
            return object;
        } catch (Throwable e) {
            System.out.println("执行方法异常：" + e.getClass().getName());
        }
        System.out.println("----------------环绕通知结束---------------");
        return null;
    }

    /**
     * 后置通知
     * @param joinPoint 切点信息
     */
    @After(value = "excute()")
    public void after(JoinPoint joinPoint) {
        System.out.println("执行方法后");
        System.out.println(joinPoint.toString());
    }

    /**
     * 后置通知 带返回值
     * @return obj
     */
    @AfterReturning(pointcut = "excute()", returning = "obj")
    public void afterReturning(Object obj) {
        System.out.println("执行方法之后获取返回值："+obj);
    }

    /**
     * 后置通知，异常执行后
     * @param e 异常对象
     */
    @AfterThrowing(pointcut = "excute()", throwing = "e")
    public void doAfterThrowing(Exception e) {
        System.out.println("执行方法异常："+e.getClass().getName());
    }


    /**
     * 获取参数Map集合
     * @param joinPoint 切点信息
     * @return param
     */
    private Map<String, Object> getNameAndValue(ProceedingJoinPoint joinPoint) {
        Map<String, Object> param = new HashMap<>(16);
        Object[] paramValues = joinPoint.getArgs();
        String[] paramNames = ((CodeSignature)joinPoint.getSignature()).getParameterNames();
        for (int i = 0; i < paramNames.length; i++) {
            param.put(paramNames[i], paramValues[i]);
        }
        return param;
    }

    /**
     * 获取用户真实IP地址，不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
     * 可是，如果通过了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP值
     *
     * @return ip
     */
    private String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if(ip.contains(",")){
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}
